import os
import re
import json
import inspect
import itertools
from pathlib import Path

import allure
import pytest
from jsonpath import jsonpath

from src.yaml_util import Yaml
from src.config_util import Config
from src.log import log, set_log_format
from src.http_session import HttpSession
from src.common import open_file_for_upload
from src.render_template import RenderTemplate
from src.allure_report import collect_test_result
from src.notify.report_notify import report_notification
from conf.setting import FILE_PATH, DATA_PATH, BASE_PATH

env_info = Config("conf/env.yml").get_conf(Config().get_conf("env"))


def __get_yaml_path_by_name(file_name, dir_path=DATA_PATH):
    """通过文件名称，查找文件，返回文件路径"""
    p = Path(dir_path)
    return list(p.rglob(f"{file_name}.yml"))[0] or list(p.rglob(f"{file_name}.yaml"))[0]


def __get_data_path(func_name, module_path):
    """通过用例名称查找对应的数据文件的路径"""
    data_dir = os.path.dirname(module_path).replace("tests", "data")
    try:
        data_path = __get_yaml_path_by_name(func_name[5:], data_dir)
    except Exception:
        data_path = __get_yaml_path_by_name(func_name[5:])
    return data_path


def __exist_file(string):
    """判断上传的文件files中是否存在"""
    return os.path.isfile(os.path.join(FILE_PATH, string))


def __replace_file(obj):
    """将files对象中的文件路径替换成文件流"""

    def inner(value_list):
        temp = list()
        for item in value_list:
            if isinstance(item, str):
                if __exist_file(item):
                    temp.append(open_file_for_upload(item))
                else:
                    temp.append(item)
            elif isinstance(item, dict):
                temp.append(__replace_file(item))
            elif isinstance(item, list):
                temp.append(inner(item))
        return temp

    for key in obj.keys():
        value = obj.get(key)
        if isinstance(value, str):
            if __exist_file(value):
                obj[key] = open_file_for_upload(value)
            else:
                obj[key] = value
        elif isinstance(value, list):
            obj[key] = inner(value)
        elif isinstance(value, dict):
            __replace_file(value)
    return obj


def __init_allure(params):
    """设置allure中case的 title, description, level"""
    case_level_map = {
        "p0": allure.severity_level.BLOCKER,
        "p1": allure.severity_level.CRITICAL,
        "p2": allure.severity_level.NORMAL,
        "p3": allure.severity_level.MINOR,
        "p4": allure.severity_level.TRIVIAL,
    }
    log.info(f"用例标题: {params.get('title')}")
    allure.dynamic.title(params.get("title"))
    allure.dynamic.description(params.get("description"))
    allure.dynamic.severity(case_level_map.get(params.get("level")))


def pytest_generate_tests(metafunc):
    """实现parameters参数化数据驱动"""
    ids = list()
    case_list = list()
    cartesian_product = list()
    func_name = metafunc.function.__name__
    module_path = metafunc.module.__file__
    data_path = __get_data_path(func_name, module_path)
    test_data = Yaml(data_path).read_yml()
    case_common = test_data.get("case_common")
    cases = test_data.get("cases")

    parameters = case_common.get("parameters") or case_common.get("p")
    # 解析parameters，进行参数化处理，生成用例列表
    if parameters:
        if isinstance(parameters, list):
            cartesian_product = parameters
        elif isinstance(parameters, dict):
            cartesian_product = list()
            value_list = list()
            keys = list(parameters.keys())
            for key in keys:
                value_list.append(parameters.get(key))
            for item in itertools.product(*value_list):
                cartesian_product.append({key: item[keys.index(key)] for key in keys})
            cartesian_product = cartesian_product

        for item in cartesian_product:
            base_case = json.dumps(cases[0])
            for key in item.keys():
                value = item.get(key)
                if isinstance(value, (dict, list)):
                    log.info(f"不支持的dict, list类型的值进行参数化, {value}为{type(value)}类型")
                    break
                else:
                    value = str(value)
                reg = re.compile(r"\$%s" % key)
                base_case = reg.sub(str(value), base_case)
            base_case = json.loads(base_case)
            base_case["request"]["url"] = case_common.get("url")
            base_case["request"]["method"] = case_common.get("method")
            case_list.append(base_case)
        if "parameters" in metafunc.fixturenames:
            metafunc.parametrize("parameters", case_list, scope="function")
    # 非参数化直接读取用例
    else:
        for case in cases:
            ids.append(case.get("title"))
            if case.get("request") is None:
                case["request"] = dict()
            case["request"]["url"] = case_common.get("url")
            case["request"]["method"] = case_common.get("method")
        if "parameters" in metafunc.fixturenames:
            metafunc.parametrize("parameters", cases, ids=ids, scope="function")


def pytest_runtest_setup(item):
    """初始化case"""

    # 设置allure报告中每个用例的， feature， story
    func_name = item.function.__name__
    module_path = str(item.path)
    data_path = str(__get_data_path(func_name, module_path))
    case_common = Yaml(data_path).read_yml("case_common")
    allure.dynamic.feature(case_common.get("feature"))
    allure.dynamic.story(case_common.get("story"))
    log.info(f"开始执行用例: {func_name}")
    log.info(f"用例所在路径: {module_path[module_path.index('/tests/') + 1:]}")
    log.info(f"用例请求参数所在路径: {data_path[data_path.index('/data/') + 1:]}")


def pytest_runtest_call(item):
    """对请求参数模版进行渲染"""
    parameters = item.funcargs.get("parameters")
    request_obj = parameters.get("request")

    url = request_obj.get("url")
    if not url.startswith("http"):
        request_obj["url"] = env_info.get("base_url") + url

    if "$" in str(request_obj):
        with allure.step("步骤一: 读取测试数据,并进行渲染"):
            request_obj = RenderTemplate(request_obj).render()
            with allure.step("渲染数据"):
                # 如果是文件上传，将参数中的文件路径转为文件流
                try:
                    files = request_obj.pop("files")
                    with allure.step(f"将文件对象{files}, 转为字节流对象"):
                        files_obj = __replace_file(files)
                except KeyError:
                    files_obj = None
                finally:
                    request_obj["files"] = files_obj
    parameters["request"] = request_obj
    item.funcargs["parameters"] = parameters
    __init_allure(parameters)
    if "requests" not in inspect.getfullargspec(item.function).args:
        requests = item.funcargs.get("requests")
        item.funcargs["response"] = requests.send_request(parameters)


def pytest_terminal_summary(terminalreporter, exitstatus, config):
    """收集测试结果"""

    statistics_result = collect_test_result(terminalreporter)
    report_notification(config.getoption("--notify"), statistics_result)

    with open(os.path.join(BASE_PATH, "reports/cases_analysis.txt"), "w") as fp:
        fp.write("TOTAL=%s" % statistics_result.total + "\n")
        fp.write("PASSED=%s" % statistics_result.passed + "\n")
        fp.write("FAILED=%s" % statistics_result.failed + "\n")
        fp.write("ERROR=%s" % statistics_result.error + "\n")
        fp.write("SKIPPED=%s" % statistics_result.skipped + "\n")
        fp.write("SUCCESSFUL=%.2f%%" % statistics_result.pass_rate + "\n")
        fp.write("TOTAL_TIMES=%s" % statistics_result.duration + "\n")
        fp.write("FAILED_CASES=%s" % statistics_result.failed_cases)


def pytest_addoption(parser):
    """注册自定义参数 env 到配置对象"""

    parser.addoption("--env", action="store",
                     default="dev",
                     choices=list(Yaml("conf/env.yml").read_yml().keys()),
                     required=True,
                     help="--env: 选择测试执行环境")

    parser.addoption("--notify", action="store",
                     default="1",
                     required=False,
                     help="--notify: 支持1，2，3，4、 4种通知方式，用逗号分割")


def pytest_cmdline_main(config):
    """保存命令行参数env到配置文件"""
    Config().set_conf("env", config.getoption("--env"))


def pytest_configure(config):
    # 配置日志文件和格式
    set_log_format(config)


def login(session):
    account = env_info.get("account")
    account["login_type"] = "pwd"
    # 调用登录方法，返回token
    resp = session._send(
        url="http://127.0.0.1:8000/api/v1/shipper/login/",
        method="post",
        json=account
    )
    # 将token加到请求头
    session.update_headers({
        "Authorization": jsonpath(resp.json(), "$.data.Authorization")[0]
    })
    return session


@pytest.fixture(scope="session", autouse=True)
def requests():
    session = HttpSession()
    yield session
    # 调用登录接口，实现全局只登录一次
    # yield login(session)
    session.close()


@pytest.fixture(autouse=True)
def response():
    resp = None
    yield resp
